本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。

セキュリティナレッジ

2026.03.19

MBSD Cybersecurity Challenges 2025 Write Up (プロトタイプ汚染とXSS)

セキュリティコンテスト

2025年末に10回目となるセキュリティコンテストMBSD Cybersecurity Challenges 2025が開催されました。毎年様々なテーマで実施していますが、今回はWebアプリケーションの脆弱性診断と報告書作成がテーマでした。私も昨年に続き審査員や脆弱性のあるWebアプリケーションの作成で関わらせてもらいました。

詳細はこちらをご覧ください。第10回セキュリティコンテスト MBSD Cybersecurity Challenges 2025

今回作問を担当した部分について社内情報共有のために資料をまとめていたのですが、プロトタイプ汚染によるXSSの発生について解説している日本語記事をあまり見かけなかったこともあり、コンテストの所感なども交えつつ問題解説の記事を執筆しました。

報告書に関する所感

多数のチームにご参加いただき、報告書を見比べながら審査担当者も頭を悩ませました。全チームの報告書に目を通しましたが、報告書に関しては以下のような観点で差が付きやすかったと感じています。

  • 脆弱性の検出率
    今回のコンテストではWebアプリケーションの脆弱性診断を主軸としていることもあり、できるだけ網羅的に多くの脆弱性を見つけることが大切です。知識や技術力ももちろんですが、画面一覧を作るなど情報共有や調査漏れを防止する試みをしているチームが印象的でした。
  • 危険度やリスクの適切さ
    実際には攻撃が成立しない問題や、緊急度が低そうな問題についても十分な説明がないまま危険度高としているレポートが見受けられました。潜在的な問題であっても修正が望ましい場合はありますが、実際には時間や予算には限りがあります。スキャンツールの出力を鵜呑みにせず、「将来的にプログラムを変更したら脆弱になるかもしれない」といった仮定を含む場合は危険度が適切かを再度検討しましょう。攻撃にハードルや条件がある場合はその旨を明記するなど、修正側の立場になって考えてみると良いかと思います。また、生成AIや自動スキャンツールを利用する場合でも、文面が現状システムにおいて当てはまるかはよく検討する必要があります。
  • 報告内容の明確さと再現性
    報告書を受け取った依頼者が、脆弱性の内容やリスクを正確に読み取れることが重要です。また、修正のために適切な対応策と、再現手順やどのパラメータで発生するかの明記も重要となります。スクリーンショットも、大量に載せると読みにくくなってしまうこともありますが、うまく使うと説明の補助として有用です。
  • 報告書の見やすさと合理性
    報告事項の内容以外に、報告書の読みやすさや合理性も重要となります。時間が限られたコンテストでデザインを整える余裕が無い場合でも、目次の作成や、報告内容を危険度順にソートするだけでも大幅に読みやすくなります。

総じて、依頼者や読み手のことを考える、現実的な問題の修正に役立つ報告書にする、といった観点が大切と感じます。実務や経験を通じてわかっていくこともありますが、学生のうちから意識してみると上達が早まるかもしれません。

プロトタイプ汚染

今回私が作問を担当した中で、難しい脆弱性としてプロトタイプ汚染によってXSSが発生する箇所を用意したのですが、確認した範囲では完答できたチームがいませんでした。問題の分量が多いこともあり、より危険度の高い項目に注目して断念した可能性もありますが、プロトタイプ汚染の理論や検証方法を広める機会として、この場で解説してみます。

問題(抜粋)

脆弱性はサイト中のクーポンコード入力ページに存在していました。このページのURLを開くと、URLのパラメーターにあるクーポンコードを入力フォームに自動で反映します。以下は再現のために抜粋したHTMLです。scriptタグでは、作問当時最新のjQuery 3.7.1と、古いバージョンのjustライブラリのobject-safe-setを使用しています。

https://jquery.com/download/

https://github.com/angus-c/just/blob/adaa253d7f56ef2903d17eb1dfad76b8a8b2f730/packages/object-safe-set/index.js

    
<script src="(jQueryのJavaScript)"></script>
<script src="(object-safe-setのJavaScript)"></script>

<input id="code" />
<div id="label">Name: </div>
<script>
function setCoupon(coupon) {
    $("#code").val(coupon.code);
    $("#label").append($("<span style='color:gray'>").text(coupon.name) )
}
$(function(){
    const params = {};
    for (const p of location.search.substr(1).split("&")) {
        const kv = p.split("=");
        set(params, kv[0], decodeURIComponent(kv[1]));
    }
    if(params.coupon) setCoupon(params.coupon);
})
</script> 

サイトの正常遷移では、以下のようなURLにアクセスすることでクーポンコード(coupon.code)と、クーポン名(coupon.name)が自動でフォームに入力されます。

http://localhost/coupon.html?coupon.code=1234ABCD&coupon.name=Sample

プロトタイプ汚染とは

まず、プロトタイプ汚染を理解するために、プロトタイプについて仕様を把握しましょう。

JavaScriptでは、オブジェクトの振る舞いに関わるプロトタイプという仕組みがあります。例えばユーザが作成した配列は通常Arrayをプロトタイプ(原型)としており、そのArrayはObjectをプロトタイプ(原型)としています。以下のようなコードでは、[1,2]というオブジェクトにtoStringというプロパティが無いかを探し、見つからない場合更に__proto__を辿って探していきます。(プロトタイプチェイン)

let x = [1,2];
x.aaa = 0;
x.find = () => {};
x.aaa // x.aaa
x.length // x.__proto__.length
x.toString // x.__proto__.__proto__.toString
x.isAdmin // 見つからないのでundefined

プロトタイプ汚染1.png

上記の通り、isAdminはプロトタイプをすべて辿っても発見できないため、undefinedとなります。ちょっとややこしい仕様のため、詳細な説明はMDNに譲ります。

継承とプロトタイプチェーン - JavaScript | MDN

さて、ここでObjectのプロトタイプである Object.prototype.isAdmin にtrueの値を設定します。これにより、配列のisAdminを参照しようとすると、プロトタイプチェインを辿ってObject.prototype.isAdminの値を参照し、trueを返します。

__proto__やprototypeといったプロパティを辿ってプロトタイプを設定できることの考慮を忘れると、開発者やユーザの意図に反してプロトタイプを設定できてしまうプロトタイプ汚染の問題に繋がります。例えば、isAdminが存在するかで分岐していた場合、管理者専用機能が不正に使用できてしまう可能性があります。これがサーバサイドのJavaScript(Node.js)などで発生した場合、認可制御の不備などの脆弱性につながるおそれがあります。

Object.prototype.isAdmin = true;
let x = [1,2]
if( x.isAdmin )// x.__proto__.__proto__.isAdmin を参照しtrue
{
  // 管理者専用機能
}

プロトタイプ汚染2.png

ブラウザ上でのプロトタイプ汚染

さて、今回の主題であるブラウザ上のプロトタイプ汚染に絞って考えてみましょう。ブラウザ上でもプロトタイプ汚染の範囲は広く、例えば以下のように、GETリクエストとして使用する想定のfetch呼び出しであっても、プロトタイプ汚染されたパラメータが解釈されることでPOSTが実行されてしまいます。

Object.prototype.method="POST";
Object.prototype.body="p=0";
fetch(”/sample”,{"mode":"cors"});
// 以下のように解釈される // fetch(”/sample”,{"mode":"cors", "method":"POST","body":"p=0"});

このような処理を探してみるのも手ではありますが、任意のスクリプトをブラウザ上で実行できてしまうXSSが最もわかりやすいゴールとなります。

XSSではsource(攻撃者の入力値などを受け取る箇所)とsink(XSSが発生する箇所)、加えてプロトタイプ汚染が発生する箇所の3点を検討することが大切です。

まずはプロトタイプ汚染が発生する箇所を探してみます。問題文で使われているjustライブラリのobject-safe-setは、以下のように深いオブジェクトを辿って値を設定できる機能です。

var obj1 = {};
set(obj1, 'a.aa.aaa', 4)
obj1; // {a: {aa: {aaa: 4}}}

このように再帰的にプロパティを設定したり、オブジェクトをコピーする箇所では__proto__やprototypeという名前をたどることでプロトタイプを汚染できる場合があります。

var obj1 = {};
set(obj1, '__proto__.xxx', 4); // obj1.__proto__.xxx = 4; が実行され、プロトタイプ汚染が発生
[1,2].xxx // 4

過去に多くのライブラリで同様の問題が発見されており、今回の出題では問題修正前の古いバージョンを(出題用にあえて)使用しています。問題ではURLのsearchからcoupon.code、coupon.nameといったオブジェクトを受け取るためにobject-safe-setを使っているため、URLがsourceとなります。被害者を不正なパラメータを付けたURLに誘導する(リンクをクリックさせるなど)だけでプロトタイプの汚染は成立します。

なお、Burp Suite内蔵ブラウザに入っている拡張機能ではプロトタイプ汚染の検出に活用できる機能が存在します。……が、状況によりうまく検出してくれないこともあります。

sinkとガジェットの活用

さて、問題はsinkの側です。例えば、このようなスクリプトがあればXSSが発生します。

// __proto__.name=<img src onerror=alert(0)
var x = {};
document.getElementById("name").innerHTML = x.name;

しかし、このように都合がよくXSSに直接つながるコードが存在することは稀で、今回の問題のように僅かに画面表示を制御するスクリプトがある程度で問題が見つからなかったり、逆に圧縮された膨大なJavaScriptが存在して詳細に追いかけることが困難な場合もあります。

今回はjQuery関数を「ガジェット」として使用するのが想定解となります。ガジェットは充電器やイヤホンなどの小型電子機器でよく使われる言葉ですが、セキュリティの世界では「ターゲット環境に存在する便利なコード片」のような意味合いで使われます。(Web分野以外だと、ROP gadgetなどの用例を聞いたことがあるかもしれません。)

プロトタイプ汚染でも、著名なライブラリがXSSを発生させるガジェットとして使えることが知られています。つまりプロトタイプ汚染が発生した状態で、特定のライブラリ関数を呼び出すことで、内部でinnerHTMLやevalなどに汚染された内容が渡り、スクリプトが実行されるのです。

以下は著名なライブラリと、どのプロパティを汚染すればよいかをまとめたサイトです。

https://github.com/BlackFan/client-side-prototype-pollution/
(弊社の管理サイトではないので、実際に成功するか含めて自己責任でご確認ください。)

実践:プロトタイプ汚染ガジェット

さて、コンテストの問題に戻ってみましょう。このサイトでは、受け取ったパラメータをもとに画面にクーポン名を表示するために以下のコードが存在します。

$("<span style='color:gray'>")

上記のBlackFanのレポジトリを探すと、以下のjQuery(html) のPoCが当てはまることがわかります。このPoCでは、Object.prototype.divに配列を設定し、一つ以上の属性を持つ要素を渡してjQuery関数が呼び出されることで、onerrorに指定されたスクリプトが実行されます。

<script/src=https://code.jquery.com/jquery-3.3.1.js></script>
<script>
  Object.prototype.div=['1','<img src onerror=alert(1)>']
</script>
<script>
  $('<div x="x"></div>')
</script>

これをコンテストの問題向けに条件の確認とカスタマイズを行います。

  • 問題となるコードはdivではなくspanタグを用いたコードである
  • sinkがURLであるため、配列そのままは指定できない
  • innerHTMLに汚染された内容が設定される状況のため、参考サイトの通り、<script>alert(0)></script> のようなscriptタグではJavaScriptが実行されない
    参考: https://html.spec.whatwg.org/dev/scripting.html#the-script-element

これらを踏まえると、模範回答は以下のようなURLになります。

http://localhost/coupon.html?coupon.code=a&__proto__.span.0=1&__proto__.span.1=%3Cimg/src/onerror%3dalert(1)%3E


XSS.pngこれで、プロトタイプ汚染によってURLを開くだけで任意のスクリプトを被害者のブラウザで実行できることが実証されました!

追補(jQueryの内部を読む)

報告書にここまで記載する必要はありませんが、せっかくの機会なのでなぜプロトタイプ汚染+jQuery(html)でXSSが発生するかを深く確認しましょう。じっくり追いかけていくと、以下のような処理に行き当たります。この処理は、thead要素はtable要素の中にしか存在できない……といったややマニアックな仕様に対処するためのものです。

https://github.com/jquery/jquery/blob/c28c26aef0b3238f578690d73703382951cb355d/test/data/jquery-3.7.1.js#L4769

(日本語コメントは筆者注)

var wrapMap = {

	// XHTML parsers do not magically insert elements in the
	// same way that tag soup parsers do. So we cannot shorten
	// this by omitting <tbody> or other required elements.
	thead: [ 1, "<table>", "</table>" ],
	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

	_default: [ 0, "", "" ]
};
// wrapMap["span"]を参照。本来ならundefinedだが、プロトタイプ汚染により"<img src onerror=alert(1)>"
wrap = wrapMap[ tag ] || wrapMap._default;
// <img src onerror=alert(1)><span style='color:gray' />undefined がinnerHTMLに代入される
tmpinnerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];

このように、wrapMap[”span”]の値が存在しないためデフォルト値を使う……というコードですが、プロトタイプ汚染により wrapMap[”span”]の値を設定できてしまうことにより、不正な値がinnerHTMLに渡りXSSが成立します。

プロトタイプ汚染の対策

今回は脆弱性診断の報告書であるため、対策も適切に記載する必要があります。

sinkの側を修正するというのがXSS対策では一般的ですが、今回のsinkはjQueryの内部であり、ライブラリを修正するような対策は不適です。また、新しいjQuery 3.7.1でも上記の問題が発生するように、ガジェットとして悪用できるライブラリ自体は脆弱であるとみなさないのが普通です。

報告書としてはプロトタイプ汚染が発生する箇所を修正することが根本かつ重要な対策となります。具体的な対策方法についてはISOG-Jの記事に譲ります。

https://wg1.isog-j.org/newtechtestdoc/docs/prototype_pollution/

今回に関しては、細かい修正方法よりも、脆弱性のある古いjustライブラリを使用していることがプロトタイプ汚染の原因であるとして、ライブラリのバージョンアップを推奨するのが最も適切と考えられます。

まとめ

今回はコンテストの参加者向けにコンテストの振り返りと、プロトタイプ汚染の解説を行いました。報告書の正解は一つではなく、お客様の要望、システムや脆弱性の状況等に応じて最適なものは変化します。とはいえ、読む人のことを考えて見やすく正確に、を常に意識しておくと良い報告書に近づくのではないかと思います。また、報告書では「現時点で実際に被害が発生するか」を考慮してリスクを評価するため、「XSSの可能性がある」と「XSSが実際に発生する」ではインパクトや報告内容が大きく変化します。今回はプロトタイプ汚染とXSSについて解説しましたが、ぜひいろいろな脆弱性の知識や検出方法を勉強してみてください。